﻿#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Gezel.Model;

#endregion

namespace Weazel.Gezel.Simulator.Reference.Setup
{
  static class ControllerSetup
  {
    /// <summary>
    /// Sets up all the controllers of a gezel model one after one
    /// </summary>
    /// <param name="sim">simulation instance</param>
    /// <param name="model">the model containing the parsed controller objects</param>
    internal static void setupController(Simulation sim, Weazel.Gezel.Model.Model model)
    {
      foreach (Weazel.Gezel.Model.Controller controller in model.Controllers.Values)
      {
        // do not try to setup controller for datapath which is not 
        // member of any system
        if (!controller.Datapath.InSystem)
          continue;

        if (controller.Type == Controller.ControllerType.Hardwired || controller.Type == Controller.ControllerType.Sequencer)
          setupSequenceController(sim, controller);
        else if (controller.Type == Controller.ControllerType.Fsm)
          setupFsmController(sim, controller);
      }
    }
    
    /// <summary>
    /// Setup a hardwired or sequence controller from the parsed
    /// controller description
    /// </summary>
    /// <param name="sim">simulation instance</param>
    /// <param name="controller">parsed controller object</param>
    private static void setupSequenceController(Simulation sim, Weazel.Gezel.Model.Controller controller)
    {
      DatapathContext datapathContext = sim.datapaths[controller.Datapath.Id].datapathContext;
      SequenceController sequenceController = new SequenceController(datapathContext, controller.Name);
      sim.controllers[controller.Id] = sequenceController;
      

      foreach (string sfgName in controller.SignalFlowGraphRefs)
      {
        sequenceController.ProgramNextstep(0, Symid.NOSYMBOL);

        int sfgRefId = controller.Datapath.SignalFlowGraphs[sfgName].Id;
        sequenceController.AddSignalFlowGraph(sfgRefId);
      }
    }

    /// <summary>
    /// Sets up a finite state machine controller from the
    /// parsed controller description
    /// </summary>
    /// <param name="sim"></param>
    /// <param name="controller"></param>
    private static void setupFsmController(Simulation sim, Weazel.Gezel.Model.Controller controller)
    {
      // add fsm to symbolNames map
      sim.symbolNames[controller.Id] = controller.Name;

      DatapathContext datapathContext = sim.datapaths[controller.Datapath.Id].datapathContext;
      FsmController fsm = new FsmController(sim, datapathContext, controller.Id, controller.Name);
      sim.controllers[controller.Id] = fsm;

      fsm.AddInitialState(controller.InitialState.Id);
      sim.symbolNames[controller.InitialState.Id] = controller.InitialState.Name;
      foreach (State state in controller.States.Values)
      {
        fsm.AddState(state.Id);
        sim.symbolNames[state.Id] = state.Name;
      }

      int controlStep = 0;
      foreach (string transitionStr in controller.Transitions.Keys)
      {
        setupTransition(sim, fsm, controller, controller.Transitions[transitionStr], controller.States[transitionStr].Id, ref controlStep, Symid.NOSYMBOL);
      }
    }

    /// <summary>
    /// Sets up a conditional. The tree different kinds of transitions 
    /// (simple unconditional, simple conditional and nested) are handled
    /// separately
    /// </summary>
    /// <param name="sim">the simulation instance</param>
    /// <param name="fsm">the fsm being setup</param>
    /// <param name="controller">parsed controller data</param>
    /// <param name="trans">the transition to setup</param>
    /// <param name="sourceStateId">source state of the transition</param>
    /// <param name="controlStep">control step of transition</param>
    /// <param name="parentCondition">set to SymId.NOSYMBOL if no parent condition</param>
    private static void setupTransition(
      Simulation sim,
      FsmController fsm,
      Weazel.Gezel.Model.Controller controller,
      Transition transition,
      Symid sourceStateId,
      ref int controlStep,
      Symid parentCondition)
    {
      if (transition.Condition != null)
      {
        setupFsmCondition(sim, transition.Condition, controller.Datapath);
      }

      if (transition.TransitionType == TransitionType.SimpleUnconditional)
      {
        System.Diagnostics.Debug.Assert(transition.SecondTargetState == null);
        System.Diagnostics.Debug.Assert(transition.SecondTargetSfgs == null);

        fsm.ProgramNextstep(controlStep++, Symid.NOSYMBOL);
        if (transition.TraceFirst)
          fsm.AddTracer();

        string[] execSfgs = transition.FirstTargetSfgs;
        for (int j = 0; j < execSfgs.Length; j++)
          fsm.AddSignalFlowGraph(controller.Datapath.SignalFlowGraphs[execSfgs[j]].Id);
        fsm.programTransition(sourceStateId, controller.States[transition.FirstTargetState].Id);
      }
      else if (transition.TransitionType == TransitionType.SimpleConditional)
      {
        System.Diagnostics.Debug.Assert(transition.FirstTargetState != null);
        System.Diagnostics.Debug.Assert(transition.FirstTargetSfgs != null);
        System.Diagnostics.Debug.Assert(transition.SecondTargetState != null);
        System.Diagnostics.Debug.Assert(transition.SecondTargetSfgs != null);

        Weazel.Gezel.Model.Datapath datapath = controller.Datapath;
        int conditionId = transition.Condition.ExpressionType == ExpressionType.Identifier ?
          SimulationSetup.getRefId(datapath, transition.Condition as LeafExpression) : transition.Condition.Id;

        //
        // setup the true branch
        //
        fsm.ProgramTrueBranch(parentCondition, conditionId, sim.signals[conditionId], sim);
        fsm.ProgramNextstep(controlStep++, conditionId);
        if (transition.TraceFirst)
          fsm.AddTracer();

        string[] execSfgs = transition.FirstTargetSfgs;
        for (int j = 0; j < execSfgs.Length; j++)
          fsm.AddSignalFlowGraph(controller.Datapath.SignalFlowGraphs[execSfgs[j]].Id);
        fsm.programTransition(sourceStateId, controller.States[transition.FirstTargetState].Id);

        //
        // setup the false branch
        //
        fsm.ProgramFalseBranch(parentCondition, conditionId, sim.signals[conditionId], sim);
        fsm.ProgramNextstep(controlStep++, conditionId);
        if (transition.TraceSecond)
          fsm.AddTracer();

        execSfgs = transition.SecondTargetSfgs;
        for (int j = 0; j < execSfgs.Length; j++)
          fsm.AddSignalFlowGraph(controller.Datapath.SignalFlowGraphs[execSfgs[j]].Id);
        fsm.programTransition(sourceStateId, controller.States[transition.SecondTargetState].Id);
      }
      else if (transition.TransitionType == TransitionType.Nested)
      {
        System.Diagnostics.Debug.Assert(transition.FirstTargetState == null);
        System.Diagnostics.Debug.Assert(transition.FirstTargetSfgs == null);
        System.Diagnostics.Debug.Assert(transition.SecondTargetState == null);
        System.Diagnostics.Debug.Assert(transition.SecondTargetSfgs == null);

        Weazel.Gezel.Model.Datapath datapath = controller.Datapath;
        int conditionId = transition.Condition.ExpressionType == ExpressionType.Identifier ?
          SimulationSetup.getRefId(datapath, transition.Condition as LeafExpression) : transition.Condition.Id;

        //
        // setup the true branch
        //
        fsm.ProgramTrueBranch(parentCondition, transition.Condition.Id, sim.signals[conditionId], sim);
        fsm.ProgramNextstep(controlStep++, transition.Condition.Id);
        if (transition.FirstTransition.TraceFirst || transition.FirstTransition.TraceSecond)
          fsm.AddTracer();

        // due to the way nested conditionals are parsed, the resulting transition
        // three is unbalanced with the first transition being the simple and
        // the second being non-simple, in order not to loose information on the
        // first condition of the first transition, set it up directly if it is
        // simple and unconditional.
        if (transition.FirstTransition.TransitionType == TransitionType.SimpleUnconditional)
        {
          System.Diagnostics.Debug.Assert(transition.FirstTransition.SecondTargetState == null);
          System.Diagnostics.Debug.Assert(transition.FirstTransition.SecondTargetSfgs == null);

          string[] execSfgs = transition.FirstTransition.FirstTargetSfgs;
          for (int j = 0; j < execSfgs.Length; j++)
          {
            fsm.AddSignalFlowGraph(controller.Datapath.SignalFlowGraphs[execSfgs[j]].Id);
          }
          fsm.programTransition(sourceStateId, controller.States[transition.FirstTransition.FirstTargetState].Id);
        }
        else
          setupTransition(sim, fsm, controller, transition.FirstTransition, sourceStateId, ref controlStep, conditionId);

        //
        // setup the false branch
        //
        fsm.ProgramFalseBranch(parentCondition, conditionId, sim.signals[conditionId], sim);
        fsm.ProgramNextstep(controlStep++, transition.Condition.Id);
        if (transition.SecondTransition.TraceFirst || transition.SecondTransition.TraceSecond)
          fsm.AddTracer();

        // recurse
        setupTransition(sim, fsm, controller, transition.SecondTransition, sourceStateId, ref controlStep, conditionId);
      }
    }

    /// <summary>
    /// Setup the signal three needed to evaluate the condition associated with a
    /// particular state. Note that the resulting signals are not placed in the
    /// general pool of sfg signals (the sim.abstractSignals hash) but rather in
    /// the private scoped hash (newSignals) of the controller
    /// </summary>
    /// <param name="sim">the simulation object</param>
    /// <param name="expr">the condition expression</param>
    /// <param name="datapath">the datapath governed by this controller</param>
    private static void setupFsmCondition(Simulation sim, Expression expr, Weazel.Gezel.Model.Datapath datapath)
    {
      if (expr is DirectiveExpression)
        throw new ApplicationException("fsm-controller can't contain directive expression");
      else if(expr.ExpressionType == ExpressionType.Assignment)
        throw new ApplicationException("fsm-controller can't contain assignment expression");

      ExpressionSetup.setupExpression(sim, expr, null, datapath);
    }
  }
}
